package com.lh.center.common.web.config;

import cn.hutool.json.JSONUtil;
import com.lh.center.common.base.session.SessionUser;
import com.lh.center.common.exception.CommonException;
import com.lh.center.common.util.LoggerUtil;
import com.lh.center.common.web.session.SessionOperation;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.ReflectUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.rpc.*;
import org.apache.dubbo.rpc.service.GenericService;
import org.springframework.beans.BeanUtils;

import java.lang.reflect.Method;

/**
 * 重写dubbo异常处理
 */
@Activate(group = {CommonConstants.PROVIDER,CommonConstants.CONSUMER},order = 100)
public class DubboExceptionFilter implements Filter {

    private final Logger logger = LoggerFactory.getLogger(DubboExceptionFilter.class);

    private static final String SESSION_USER_KEY = "SESSION_USER_KEY";

    private static final String LOG_TRANCE_ID_KEY = "LOG_TRANCE_ID_KEY";

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        Result result = null;
        if(RpcContext.getContext().isConsumerSide()){
            //消费者需要传递当前登陆人信息
            SessionUser sessionUser = SessionOperation.user.get();
            if(sessionUser != null){
                SessionUser base = new SessionUser();
                BeanUtils.copyProperties(sessionUser,base);
                RpcContext.getContext().setAttachment(SESSION_USER_KEY, JSONUtil.toJsonStr(base));
            }
            String name = System.getProperty("project.name");
            RpcContext.getContext().setAttachment(LOG_TRANCE_ID_KEY, name+":"+LoggerUtil.getTranceId());
            result = invoker.invoke(invocation);
        }else if(RpcContext.getContext().isProviderSide()){
            //提供服务者可以从sessionOperation中获取相关登陆信息
            String sessionUser = RpcContext.getContext().getAttachment(SESSION_USER_KEY);
            if(StringUtils.isNotEmpty(sessionUser)){
                SessionOperation.dubboConsumerUser.set(JSONUtil.toBean(sessionUser,SessionUser.class));
            }
            String tranceId = RpcContext.getContext().getAttachment(LOG_TRANCE_ID_KEY);
            //设置当前线程日志中的transId
            LoggerUtil.setTranceId(tranceId);
            result = invoker.invoke(invocation);
            SessionOperation.dubboConsumerUser.remove();
            LoggerUtil.clearTransId();
        }else{
            result = invoker.invoke(invocation);
        }
        post(result,invoker,invocation);
        return result;
    }

    private void post(Result appResponse, Invoker<?> invoker, Invocation invocation){
        if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
            try {
                Throwable exception = appResponse.getException();

                // directly throw if it's checked exception
                if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
                    return;
                }
                // directly throw if the exception appears in the signature
                try {
                    Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
                    Class<?>[] exceptionClassses = method.getExceptionTypes();
                    for (Class<?> exceptionClass : exceptionClassses) {
                        if (exception.getClass().equals(exceptionClass)) {
                            return;
                        }
                    }
                } catch (NoSuchMethodException e) {
                    return;
                }

                // for the exception not found in method's signature, print ERROR message in server's log.
                if(!(exception instanceof CommonException)){
                    logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". dubbo: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
                }
                // directly throw if exception class and interface class are in the same jar file.
                String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
                String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
                    return;
                }
                // directly throw if it's JDK exception
                String className = exception.getClass().getName();
                if (className.startsWith("java.") || className.startsWith("javax.")) {
                    return;
                }
                // directly throw if it's dubbo exception
                if (exception instanceof RpcException) {
                    return;
                }
                if (exception instanceof CommonException) {
                    CommonException ex = (CommonException) exception;
                    RpcException re = new RpcException(ex.getErrorCode().getCode(),ex.getMessage());
                    re.setStackTrace(ex.getStackTrace());
                    re.setCode(ex.getErrorCode().getCode());
                    appResponse.setException(re);
                    return;
                }

                // otherwise, wrap with RuntimeException and throw back to the client
                appResponse.setException(new RuntimeException(StringUtils.toString(exception)));
            } catch (Throwable e) {
                logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". dubbo: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
            }
        }
    }
}
